package cn.flying.cloud.oauth.server.configuration.filter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;
import java.text.ParseException;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import com.alibaba.fastjson2.JSON;
import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTParser;

import cn.flying.cloud.base.common.constants.Constants;
import cn.flying.cloud.base.common.enums.RtCodeEnum;
import cn.flying.cloud.base.common.utils.Ft;
import cn.flying.cloud.base.common.vo.Rt;
import cn.flying.cloud.base.redis.service.RedisService;
import cn.flying.cloud.oauth.server.configuration.details.CustomUserDetailService;
import cn.flying.cloud.oauth.server.configuration.details.UserDetail;
import cn.flying.cloud.oauth.server.configuration.exception.TokenExpiredException;
import cn.flying.cloud.oauth.server.configuration.exception.TokenIllegalException;
import cn.flying.cloud.oauth.server.configuration.handler.CustomAuthorizedEntryPointHandler;

/**
 * 对所有请求进行过滤，验证携带的Token，验证成功后，将登录主体信息放入 SecurityContextHolder 中即可，
 * BasicAuthenticationFilter继承于OncePerRequestFilter==》确保在一次请求只通过一次filter，而不需要重复执行
 *
 * @author: admin
 * @date: 2023年06月16日 14:54
 * @version: 1.0
 */
public class JwtPreAuthenticationFilter extends BasicAuthenticationFilter {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private CustomUserDetailService userDetailService;
    private RedisService redisService;
    private CustomAuthorizedEntryPointHandler customauthorizedEntryPointHandler;

    /**
     * @param authenticationManager the bean to submit authentication requests to
     */
    public JwtPreAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    /**
     * 从request中读取Token
     *
     * @param request
     * @param response
     * @param chain
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 如果uri是/oauth/login/token，且为密码模式，校验验证码
        String uri = request.getRequestURI();
        String grantType = request.getParameter("grant_type");
        String captcha = request.getParameter("captcha");
        String uid = request.getParameter("uid");
        if ("/oauth/token".equals(uri) && "password".equals(grantType)) {
            String code = (String) redisService.get(Constants.AUTH_CODE + ":captcha:" + uid);
            if (StringUtils.isBlank(code) || !code.equalsIgnoreCase(captcha)) {
                logger.warn("验证码校验失败，缓存【{}】，传入参数【{}】", code, captcha);
                response.setContentType("application/json;charset=utf-8");
                PrintWriter out = response.getWriter();
                out.write(JSON.toJSONString(Rt.error(RtCodeEnum.R10004)));
                out.flush();
                out.close();
                return;
            }
        }
        //校验token
        //从头部获取token信息
        String accessToken = Ft.Servlet.getToken(request);
        //如果请求参数中也不存在token信息，此处不处理，由后续filter处理
        if (Ft.String.isBlank(accessToken)) {
            super.doFilterInternal(request, response, chain);
            return;
        }
        if (accessToken.startsWith("Bearer ")) {
            accessToken = accessToken.replace("Bearer ", "");
        }
        String jwtId;
        try {
            JWT jwt = JWTParser.parse(accessToken);
            jwtId = jwt.getJWTClaimsSet().getJWTID();
        } catch (ParseException e) {
            logger.error("Token解析失败", e);
            throw new TokenIllegalException(RtCodeEnum.R10005.getDesc());
        }
        //请求中有Token，则判断redis中是否存在该token
        String tokenKey = Constants.ACCESS_TOKEN + ":" + jwtId;
        try {
            String tokenInfo = (String) redisService.get(tokenKey);
            //如果redis里面没有token,说明该token失效
            if (Ft.String.isBlank(tokenInfo)) {
                throw new TokenExpiredException(RtCodeEnum.R10005.getDesc());
            }
            //如果存在token，则从token中解析出用户名
            UserDetail userDetail = JSON.parseObject(tokenInfo, UserDetail.class);
            //如果用户名为空，则解析失败
            if (Ft.String.isBlank(userDetail.getUsername())) {
                throw new TokenIllegalException(RtCodeEnum.R10005.getDesc());
            }
            //从数据库获取用户信息
            UserDetails userDetails = userDetailService.loadUserByUsername(userDetail.getUsername());
            //判断用户信息是否为空
            if (userDetails == null) {
                throw new TokenIllegalException(RtCodeEnum.R10005.getDesc());
            }
            //创建身份验证对象
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            //将认证主体设置到Spring Security上下文
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        } catch (AuthenticationException e) {
            customauthorizedEntryPointHandler.commence(request, response, e);
            //这里要结束程序执行，不然后续的filter还会执行，造成报错
            return;
        } catch (Exception e) {
            AuthenticationException exception = new TokenIllegalException(RtCodeEnum.R10005.getDesc(), e);
            customauthorizedEntryPointHandler.commence(request, response, exception);
            //这里要结束程序执行，不然后续的filter还会执行，造成报错
            return;
        }
        super.doFilterInternal(request, response, chain);
    }

    public CustomUserDetailService getUserDetailService() {
        return userDetailService;
    }

    public void setUserDetailService(CustomUserDetailService userDetailService) {
        this.userDetailService = userDetailService;
    }

    public RedisService getRedisService() {
        return redisService;
    }

    public void setRedisService(RedisService redisService) {
        this.redisService = redisService;
    }

    public CustomAuthorizedEntryPointHandler getUnauthorizedEntryPointHandler() {
        return customauthorizedEntryPointHandler;
    }

    public void setUnauthorizedEntryPointHandler(CustomAuthorizedEntryPointHandler customauthorizedEntryPointHandler) {
        this.customauthorizedEntryPointHandler = customauthorizedEntryPointHandler;
    }
}
